package net.bithappesn.hnqe.traverse;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import net.bithappens.hnqe.InvalidDataException;
import net.bithappens.hnqe.graph.GraphElement;
import net.bithappens.hnqe.graph.GraphRelation;
import net.bithappens.hnqe.graph.GraphRelationMember;

import org.openjena.atlas.lib.NotImplemented;
import org.openjena.atlas.logging.Log;

public class GraphPath {

	private GraphRelationMember pathStart;
	private GraphRelationMember pathEnd;
	private List<PathElement> path = new ArrayList<PathElement>();
	

	
	private class PathElement{
		public PathElement(GraphRelation relation, GraphRelationMember node) {
			super();
			this.relation = relation;
			this.member = node;
		}
		GraphRelation relation;
		GraphRelationMember member;
	}
	

	
	public GraphPath(GraphRelationMember pathStart, GraphRelationMember pathEnd) throws InvalidDataException {
		checkForNotNull(pathStart, "the start element must not be null and provide an uri");
		checkForNotNull(pathEnd, "the end element value must not be null and provide an uri");
		this.pathStart = pathStart;
		this.pathEnd = pathEnd;
	}

	private void checkForNotNull(GraphElement item, String text) throws InvalidDataException{
		if(item == null || item.getUri() == null){
			throw new InvalidDataException(text);
		}
	}
	

	public void addEdge(GraphRelation relation, GraphRelationMember member) {
		path.add(new PathElement(relation, member));
	}
	
	
	public GraphRelationMember getLatestNodeInPath() {
		if(path.isEmpty()){
			return pathStart;
		} else {
			return path.get(path.size()-1).member;
		}
	}

	public GraphRelation getLatestRelationInPath() {
		if(path.isEmpty()){
			return null;
		} else {
			return path.get(path.size()-1).relation;
		}
	}
	

	public boolean isFinished() {
		if(getLatestNodeInPath().getUri().equals(pathEnd.getUri()));
		return getLatestNodeInPath().getUri().equals(pathEnd.getUri());
	}


	public int length() {
		return path.size();
	}


	public GraphPath clonePath() throws InvalidDataException {
		GraphPath clone = new GraphPath(pathStart, pathEnd);
		
		for(PathElement element : path){
			clone.addEdge(element.relation, element.member);
		}
		
		return clone;
	}

	public Iterator<GraphRelation> getRelationIterator(){
		return new PathRelationIter(path.iterator());
	}

	public Iterator<GraphRelationMember> getNodeIterator(){
		return new PathNodeIter(path.iterator());
	}

	public Iterator<GraphElement> getPathIterator(){
		return new PathIter(path.iterator());
	}
	
	
	
	@Override
	public String toString() {
		String pathAsString = "";
		
		Iterator<GraphElement> iter = getPathIterator();
		while(iter.hasNext()){
			pathAsString += iter.next() + "\n";
		}
		
		return "GraphPath [pathStart=" + pathStart + ", pathEnd=" + pathEnd + ", path=\n" + pathAsString + "]";
	}



	private class PathRelationIter implements Iterator<GraphRelation>{

		private Iterator<PathElement> pathElementIter;
		
		public PathRelationIter(Iterator<PathElement> pathElementIter){
			this.pathElementIter = pathElementIter;
		}
		
		@Override
		public boolean hasNext() {
			return pathElementIter.hasNext();
		}

		@Override
		public GraphRelation next() {
			return pathElementIter.next().relation;
		}

		@Override
		public void remove() {
			throw new NotImplemented();		
		}
	}
	
	private class PathNodeIter implements Iterator<GraphRelationMember>{

		private Iterator<PathElement> pathElementIter;
		private boolean first = true;
		
		public PathNodeIter(Iterator<PathElement> pathElementIter){
			this.pathElementIter = pathElementIter;
		}
		
		@Override
		public boolean hasNext() {
			if(first){
				return true;
			}
			return pathElementIter.hasNext();
		}

		@Override
		public GraphRelationMember next() {
			if(first){
				first = false;
				return pathStart;
			}
			return pathElementIter.next().member;
		}

		@Override
		public void remove() {
			throw new NotImplemented();	
		}
	}
	
	private class PathIter implements Iterator<GraphElement>{

		private Iterator<PathElement> pathElementIter;

		private int counter = 0;
		
		private PathElement currentPathElement;
		
		
		
		public PathIter(Iterator<PathElement> pathElementIter){
			this.pathElementIter = pathElementIter;
		}
		
		@Override
		public boolean hasNext() {
			
			if(counter == 0){
				return true;
			}
			
			return pathElementIter.hasNext() || (counter % 2) == 0; 
		}

		@Override
		public GraphElement next() {
			if(counter == 0){
				counter++;
				return pathStart;
			}
			
			if((counter % 2) == 0){
				counter++;
				return currentPathElement.member;
			} else {
				counter++;
				currentPathElement = pathElementIter.next();
				return currentPathElement.relation;
			}
			
		}

		@Override
		public void remove() {
			throw new NotImplemented();			
		}
	}

	
}
